<!DOCTYPE html>
<html lang="en">
<head>
  <title>three.js webgl - physically based shading</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  <link type="text/css" rel="stylesheet" href="main.css">
</head>

<body>
<div id="info">
  <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - webgl physically based shading testbed
</div>

<script type="module">

  import * as THREE from '../build/three.module.js';

  import Stats from './jsm/libs/stats.module.js';

  import { GUI } from './jsm/libs/dat.gui.module.js';
  import { TrackballControls } from './jsm/controls/TrackballControls.js';

  let RC = [
    [[],[],[]],
    [[],[],[]],
    [[],[],[]]
  ]
  
  let group
  
  let stats;

  let camera, scene, renderer;

  let mesh;

  let control;

  let sunLight, ambientLight;
  
  let gui, shadowCameraHelper;
  
  let mouse,
    raycaster,
    direction,
    checkedObj,
    rotationVal = 0,
    rotationTotalVal = 0,
    rotationObjList = [],
    SRIndex = [],
    face
  
  const shadowConfig = {
    shadowCameraVisible: false,
    shadowCameraNear: 750,
    shadowCameraFar: 4000,
    shadowBias: - 0.0002
  }
  
  const scale = [-75, 0, 75]
  
  const directionMap = {
    left: 'left',
    right: 'right',
    up: 'up',
    down: 'down'
  }
  const rotateDirectionMap = {
    clockwise: [directionMap.up, directionMap.right],
    anticlockwise: [directionMap.left, directionMap.down]
  }
  
  init();
  animate();

  function init() {
    const container = document.createElement( 'div' );
    document.body.appendChild( container );

    // CAMERA
    camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 2, 10000 );
    camera.position.set( 500, 400, 1000 );
    camera.lookAt( 0, 0, 0 );
    
    // SCENE
    scene = new THREE.Scene();
    scene.background = new THREE.Color(0xfffffff);

    scene.add(new THREE.AxisHelper(2000));
    
    raycaster = new THREE.Raycaster();
    mouse = new THREE.Vector2();
    
    generateSC()
    function generateSC () {
      const materialLambert = new THREE.MeshPhongMaterial( {
        shininess: 50,
        color: 0x000000,
        flatShading: true
      });
      const cubeGeometry = new THREE.BoxGeometry( 75, 75, 75 );
      
      const roundedRectShape = new THREE.Shape();
      ( function roundedRect( ctx, x, y, z, width, height, radius ) {

        ctx.moveTo( x, y + radius );
        ctx.lineTo( x, y + height - radius );
        ctx.quadraticCurveTo( x, y + height, x + radius, y + height );
        ctx.lineTo( x + width - radius, y + height );
        ctx.quadraticCurveTo( x + width, y + height, x + width, y + height - radius );
        ctx.lineTo( x + width, y + radius );
        ctx.quadraticCurveTo( x + width, y, x + width - radius, y );
        ctx.lineTo( x + radius, y );
        ctx.quadraticCurveTo( x, y, x, y + radius );
      })( roundedRectShape, -35.5, -35.5, 0, 71, 71, 10 );

      group = new THREE.Group()
      scene.add(group)

      group.add(addObject(cubeGeometry, materialLambert, 75, 75, 75, 0))
      group.add(addShape(roundedRectShape, 0x3D81F6, 75 - 38, 75, 75, 0, Math.PI / 2, 0, 1))
      group.add(addShape(roundedRectShape, 0x009d54, 75 + 38, 75, 75, 0, Math.PI / 2, 0, 2))
      group.add(addShape(roundedRectShape, 0xdc422f, 75, 75 - 38, 75, Math.PI / 2, 0, 0, 3))
      group.add(addShape(roundedRectShape, 0xdc422f, 75, 75 - 38, 75, Math.PI / 2, 0, 0, 3))
      group.add(addShape(roundedRectShape, 0xfdcc09, 75, 75 + 38, 75, Math.PI / 2, 0, 0, 4))
      group.add(addShape(roundedRectShape, 0xff6c00, 75, 75, 75 - 38, 0, 0, 0, 5))
      group.add(addShape(roundedRectShape, 0xffffff, 75, 75, 75 + 38, 0, 0, 0, 6))

    }
    function addShape( shape, color, x, y, z, rx, ry, rz, face ) {
      let geometry = new THREE.ShapeGeometry( shape )
      let mesh = new THREE.Mesh(
        geometry,
        new THREE.MeshPhongMaterial({
          color: color,
          side: THREE.DoubleSide
        }));
      mesh.position.set( x, y, z)
      mesh.rotation.set( rx, ry, rz )
      mesh.face = face
      return mesh
    }
    
    function addObject( geometry, material, x, y, z, ry ) {
      const tmpMesh = new THREE.Mesh( geometry, material );
      tmpMesh.material.color.offsetHSL( 0.1, - 0.1, 0 );
      tmpMesh.position.set( x, y, z );
      tmpMesh.rotation.y = ry;
      tmpMesh.castShadow = true;
      tmpMesh.receiveShadow = true;
      return tmpMesh;
    }

    // LIGHTS
    ambientLight = new THREE.AmbientLight( 0xfffffff );
    scene.add( ambientLight );
    sunLight = new THREE.DirectionalLight( 0xffffff, 0.3 );
    sunLight.position.set( 1000, 2000, 1000 );
    sunLight.castShadow = true;
    sunLight.shadow.camera.top = 750;
    sunLight.shadow.camera.bottom = - 750;
    sunLight.shadow.camera.left = - 750;
    sunLight.shadow.camera.right = 750;
    sunLight.shadow.camera.near = shadowConfig.shadowCameraNear;
    sunLight.shadow.camera.far = shadowConfig.shadowCameraFar;
    sunLight.shadow.mapSize.set( 1024, 1024 );
    sunLight.shadow.bias = shadowConfig.shadowBias;
    scene.add( sunLight );

    // SHADOW CAMERA HELPER

    shadowCameraHelper = new THREE.CameraHelper( sunLight.shadow.camera );
    shadowCameraHelper.visible = shadowConfig.shadowCameraVisible;
    scene.add( shadowCameraHelper );

    // RENDERER

    renderer = new THREE.WebGLRenderer( { antialias: true } );
    renderer.setPixelRatio( window.devicePixelRatio );
    renderer.setSize( window.innerWidth, window.innerHeight );
    container.appendChild( renderer.domElement );

    //

    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    renderer.outputEncoding = THREE.sRGBEncoding;

    //
    control = new TrackballControls( camera, renderer.domElement );
    control.target.set( 0, 120, 0 );
    control.rotateSpeed = 1.0;
    control.zoomSpeed = 1.2;
    control.panSpeed = 0.8;

    control.staticMoving = true;

    control.keys = [ 'KeyA', 'KeyS', 'KeyD' ];


    // STATS

    stats = new Stats();
    container.appendChild( stats.dom );

    // EVENTS
    window.addEventListener( 'resize', onWindowResize );

    // GUI
    gui = new GUI( { width: 400 } );

    // SHADOW
    const shadowGUI = gui.addFolder( "Shadow" );
    shadowGUI.add( shadowConfig, 'shadowCameraVisible' ).onChange( function () {
      shadowCameraHelper.visible = shadowConfig.shadowCameraVisible;
    });

    shadowGUI.add( shadowConfig, 'shadowCameraNear', 1, 1500 ).onChange( function () {
      sunLight.shadow.camera.near = shadowConfig.shadowCameraNear;
      sunLight.shadow.camera.updateProjectionMatrix();
      shadowCameraHelper.update();
    } )

    shadowGUI.add( shadowConfig, 'shadowCameraFar', 1501, 5000 ).onChange( function () {
      sunLight.shadow.camera.far = shadowConfig.shadowCameraFar;
      sunLight.shadow.camera.updateProjectionMatrix();
      shadowCameraHelper.update();
    })

    shadowGUI.add( shadowConfig, 'shadowBias', - 0.01, 0.01 ).onChange( function () {
      sunLight.shadow.bias = shadowConfig.shadowBias;
    });
    shadowGUI.open();
    
    window.addEventListener( 'pointerdown', pointerdown, true );
    window.addEventListener( 'pointerup', pointerup, false );
    window.addEventListener( 'mousemove', onMouseMove, false );
  }

  //

  function onWindowResize() {

    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize( window.innerWidth, window.innerHeight );

    control.handleResize();

  }
  
  function pointerup (event) {
    if (!control.enabled) {
      let x = (event.clientX / window.innerWidth) * 2 - 1
      let y = - (event.clientY / window.innerHeight) * 2 + 1
      let differenceX = x - mouse.x
      let differenceY = y - mouse.y
      if (differenceY === 0 && differenceX === 0) {
        return
      }

      if (face === 2) {
        direction = Math.abs(differenceX/differenceY) > 1
          ? (x > mouse.x ? directionMap.right : directionMap.left)
          : (y > mouse.y ? directionMap.down : directionMap.up)
      } else if (face === 4) {
        direction = Math.abs(differenceX/differenceY) > 1
          ? (x > mouse.x ? directionMap.left : directionMap.right)
          : (y > mouse.y ? directionMap.up : directionMap.down)
      } else {
        direction = Math.abs(differenceX/differenceY) > 1
          ? (x > mouse.x ? directionMap.right : directionMap.left)
          : (y > mouse.y ? directionMap.up : directionMap.down)
      }
     console.info(direction)
 
      
      rotationTotalVal = Math.PI / 2
      console.info(direction, group)
    }
  }
  
  function onMouseMove (event) {
  }
  
  function pointerdown( event ) {
    event.preventDefault();
    mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
    mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
    raycaster.setFromCamera( mouse, camera );
    let intersects = raycaster.intersectObjects(scene.children, true)
    if (intersects.length > 1) {
      checkedObj = intersects[0].object.parent
      face = intersects[0].object.face
      control.enabled = false
    } else {
      control.enabled = true
    }
  }
  //

  function animate() {
    requestAnimationFrame( animate );
    stats.begin();
    render();
    stats.end();

  }
  
  function render() {
    if (rotationTotalVal > 0) {
      rotationVal = Math.PI / 90
      rotationTotalVal -= rotationVal
    } else if(rotationVal) {
      // const a = [[],[],[]]
      // for (let i = 0; i < rotationObjList.length; i++) {
      //   let index = rotationObjList[i].index
      //   a[index[SRIndex[0]]][index[SRIndex[1]]] = rotationObjList[i]
      // }
      // rotate(a)
      // for (let i = 0; i < a.length; i++) {
      //   for (let n = 0; n < a[i].length; n++) {
      //     let index = a[i][n].index
      //     scene.add(a[i][n])
      //     RC[index[0]][index[1]][index[2]] = a[i][n]
      //   }
      // }
      rotationVal = 0
    }


    
    if (rotationVal) {
      let quaternion = new THREE.Quaternion();
      // quaternion.setFromAxisAngle(new THREE.Vector3(1, 0, 0), rotationVal);
      // group.quaternion.premultiply(quaternion);
      if (direction === directionMap.left) {
        console.info('direction 3')
        quaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), -rotationVal);
        group.quaternion.premultiply(quaternion);
        // group.rotation.y -= rotationVal
      } else if (direction === directionMap.right) {
        console.info('direction 4')
        quaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), rotationVal);
        group.quaternion.premultiply(quaternion);
        // group.rotation.y += rotationVal
      } else if (direction === directionMap.up) {
        quaternion.setFromAxisAngle(new THREE.Vector3(1, 0, 0), -rotationVal);
        group.quaternion.premultiply(quaternion);
        console.info('direction 5')
        // group.rotation.x -= rotationVal
      } else if (direction === directionMap.down) {
        quaternion.setFromAxisAngle(new THREE.Vector3(1, 0, 0), rotationVal);
        group.quaternion.premultiply(quaternion);
        console.info('direction 6')
        // group.rotation.x += rotationVal
      }
    }
    
    // update
    control.update()
    renderer.render( scene, camera )
  }

  function rotate(martix) {
    if (!martix[0].length) {
      return
    }
    let tR = 0;
    let tC = 0;
    let dR = martix.length - 1;
    let dC = martix[0].length - 1;
    while(tR < dR) {
      rotateEdge(
        martix,
        tR++,
        tC++,
        dR--,
        dC--,
        martix[1][1].children[1] ? martix[1][1].children[1].face : null
      )
    }
    return martix
  }

  function rotateEdge(martix, tR, tC, dR, dC, rotateFace) {
    let times = dC - tC
    let temp = 0
    let temp2
    if (rotateDirectionMap.clockwise.includes(direction)) {
      for (let i = 0; i !== times; i++) {
        temp = martix[tR][tC + i]
        martix[tR][tC+i] = martix[dR-i][tC]
        martix[dR-i][tC] = martix[dR][dC-i]
        martix[dR][dC-i] = martix[tR+i][dC]
        martix[tR+i][dC] = temp
        
        temp2 = {
          index: martix[tR+i][dC].index,
          children: []
        }
        martix[tR+i][dC].children.map(item => {
          temp2.children.push({...item})
        })
        
        martix[tR+i][dC] = faceReplace(martix[tR+i][dC], martix[dR][dC-i], rotateFace)
        martix[dR][dC-i] = faceReplace(martix[dR][dC-i], martix[dR-i][tC], rotateFace)
        martix[dR-i][tC] = faceReplace(martix[dR-i][tC], martix[tR][tC+i], rotateFace)
        martix[tR][tC+i] = faceReplace(martix[tR][tC+i], temp2, rotateFace)
        
      }
    } else {
      for (let i = 0; i !== times; i++) {
        temp = martix[tR][tC + i]
        martix[tR][tC + i] = martix[tR + i][dC]
        martix[tR + i][dC] = martix[dR][dC - i]
        martix[dR][dC - i] = martix[dR - i][tC]
        martix[dR - i][tC] = temp

        temp2 = {
          index: martix[dR - i][tC].index,
          children: []
        }
        martix[dR - i][tC].children.map(item => {
          temp2.children.push({...item})
        })

        martix[dR - i][tC] = faceReplace(martix[dR - i][tC], martix[dR][dC - i], rotateFace)
        martix[dR][dC - i] = faceReplace(martix[dR][dC - i], martix[tR + i][dC], rotateFace)
        martix[tR + i][dC] = faceReplace(martix[tR + i][dC], martix[tR][tC + i], rotateFace)
        martix[tR][tC + i] = faceReplace(martix[tR][tC + i], temp2, rotateFace)
      }
    }
  }

  /**
   * 面旋转
   * @param obj 被替换的
   * @param obj2 替换的
   * @param rotateFace
   * @returns {*}
   */
  function faceReplace (obj, obj2, rotateFace) {
    let faceArr = []
    let faceArr2 = []
    let objArr = []

    obj.children.map((item, index) => {
      if (item.face && item.face !== rotateFace) {
        faceArr.push(item.face)
        objArr.push({
          face: item.face,
          index: index
        })
      }
    })
    
    obj2.children.map(item => {
      if (item.face && item.face !== rotateFace) {
        faceArr2.push(item.face)
      }
    })
   
    if (faceArr2.length === 2) {
      let different = faceArr2.filter(v => !faceArr.includes(v))[0]
      let same = faceArr2.filter(v => faceArr.includes(v))[0]
      if (faceArr[0] !== different && faceArr[0] !== same) {
        objArr[0].face = same
        objArr[1].face = different
      } else {
        objArr[0].face = different
        objArr[1].face = same
      }
      obj.children[objArr[0].index].face = objArr[0].face
      obj.children[objArr[1].index].face = objArr[1].face
    } else {
      obj.children[objArr[0].index].face = faceArr2[0]
    }
    obj.index = obj2.index
    return obj
  }
</script>

</body>
</html>
